home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 8: LINUX Games
/
Linux Cubed Series 8 - LINUX Games.iso
/
games
/
x11
/
rpg
/
crossfir.92
/
crossfir
/
crossfire-0.92.5
/
common
/
map.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-07-24
|
44KB
|
1,715 lines
/*
* static char *rcsid_map_c =
* "$Id: map.c,v 1.45 1996/06/10 06:20:13 master Exp $";
*/
/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 1992 Frank Tore Johansen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The author can be reached via e-mail to frankj@ifi.uio.no.
*/
#include <global.h>
#include <funcpoint.h>
#if defined(UNIQUE_ITEMS) && defined (LOCK_ITEMS)
#include <dirent.h>
#endif
#include <gods.h>
extern int nrofallocobjects,nroffreeobjects;
#if defined (MACH) || defined (NeXT) || defined (__MACH__)
#ifndef S_IWOTH
#define S_IWOTH 0000200
#endif
#ifndef S_IWGRP
#define S_IWGRP 0000020
#endif
#ifndef S_IWUSR
#define S_IWUSR 0000002
#endif
#ifndef S_IROTH
#define S_IROTH 0000400
#endif
#ifndef S_IRGRP
#define S_IRGRP 0000040
#endif
#ifndef S_IRUSR
#define S_IRUSR 0000004
#endif
#endif
#if defined(MACH) || defined(vax) || defined(ibm032) || defined(NeXT) || defined(__MACH__)
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
#endif
#ifndef S_ISREG
#define S_ISREG(x) (((x) & S_IFMT) == S_IFREG)
#endif
#endif
/* This is a list of the suffix, uncompress and compress functions. Thus,
* if you have some other compress program you want to use, the only thing
* that needs to be done is to extended this.
* The first entry must be NULL - this is what is used for non
* compressed files.
*/
char *uncomp[NROF_COMPRESS_METHODS][3] = {
{NULL, NULL, NULL},
#ifdef COMPRESS_SUFFIX
{COMPRESS_SUFFIX, UNCOMPRESS, COMPRESS},
#endif
{".Z", "uncompress", "compress"},
{".gz", "gunzip", "gzip"}
};
/*
* Returns the mapstruct which has a name matching the given argument.
*/
mapstruct *has_been_loaded (char *name) {
mapstruct *map;
if (!name || !*name)
return 0;
for (map = first_map; map; map = map->next)
if (!strcmp (name, map->path))
break;
return (map);
}
/*
* This makes a path absolute outside the world of Crossfire.
* In other words, it prepends LIBDIR/MAPDIR/ to the given path
* and returns the pointer to a static array containing the result.
*/
char *create_pathname (char *name) {
static char buf[MAX_BUF];
if (*name == '/')
sprintf (buf, "%s/%s%s", LibDir, MapDir, name);
else
sprintf (buf, "%s/%s/%s", LibDir, MapDir, name);
return (buf);
}
#ifdef UNIQUE_ITEMS
/*
* This makes absolute path to the itemfile where unique objects
* will be saved. Converts '/' to '@'. I think it's essier maintain
* files than full directory structure, but if this is problem it can
* be changed.
*/
static char *create_items_path (char *s) {
static char buf[MAX_BUF];
char *t;
if (*s == '/')
s++;
sprintf (buf, "%s/%s/", LibDir, ItemsDir);
for (t=buf+strlen(buf); *s; s++,t++)
if (*s == '/')
*t = '@';
else
*t = *s;
*t = 0;
return (buf);
}
#ifdef LOCK_ITEMS
/*
* Return 1 if lock is created, 0 if file is locked, -1 other error
*/
static int lock_unique_items(mapstruct *map, int v)
{
char buf[MAX_BUF];
int fd;
sprintf (buf, "%s.l%02d", create_items_path(map->path), v);
fd = open (buf, O_CREAT|O_EXCL, 0644);
if (fd < 0)
if (errno == EEXIST)
return 0;
else
return -1;
close (fd);
map->lock_items = v;
return 1;
}
static void unlock_unique_items(mapstruct *m) {
char buf[MAX_BUF];
if (m->lock_items) {
sprintf (buf, "%s.l%02d", create_items_path(m->path), m->lock_items);
m->lock_items = 0;
unlink(buf);
}
}
void clean_lockfiles() {
mapstruct *m;
LOG(llevDebug, "Cleaning unique items filelocks.\n");
for(m=first_map; m!=NULL; m=m->next)
if (m->lock_items)
unlock_unique_items(m);
}
#endif /* LOCK_ITEMS */
#endif /* UNIQUE_ITEMS */
/*
* This function checks if a file with the given path exists.
* -1 is returned if it failes, otherwise the mode of the file
* is returned.
* It tries out all the compression suffixes listed in the uncomp[] array.
*/
int check_path (char *name)
{
char buf[MAX_BUF], *endbuf;
struct stat statbuf;
int mode = 0, i;
strcpy (buf, create_pathname(name));
if ((endbuf = strchr(buf, '\0')) == NULL)
return (-1);
for (i = 0; i < NROF_COMPRESS_METHODS; i++) {
if (uncomp[i][0])
strcpy(endbuf, uncomp[i][0]);
else
*endbuf = '\0';
if (!stat (buf, &statbuf))
break;
}
if (i == NROF_COMPRESS_METHODS)
return (-1);
if (!S_ISREG (statbuf.st_mode))
return (0);
if (((statbuf.st_mode & S_IRGRP) && getegid() == statbuf.st_gid) ||
((statbuf.st_mode & S_IRUSR) && geteuid() == statbuf.st_uid) ||
(statbuf.st_mode & S_IROTH))
mode |= 4;
if ((statbuf.st_mode & S_IWGRP && getegid() == statbuf.st_gid) ||
(statbuf.st_mode & S_IWUSR && geteuid() == statbuf.st_uid) ||
(statbuf.st_mode & S_IWOTH))
mode |= 2;
return (mode);
}
void dump_map_lights(mapstruct *m) {
objectlink *obl=m->light;
object *tmp=NULL;
while(obl) {
if((tmp=obl->ob))
LOG(llevDebug,"%s (%d) radius:%d\n",tmp->name,tmp->count,tmp->glow_radius);
obl=obl->next;
}
}
/*
* Prints out debug-information about a map.
*/
void dump_map(mapstruct *m) {
LOG(llevError,"Map %s status: %d.\n",m->path,m->in_memory);
LOG(llevError,"Size: %dx%d Start: %d,%d\n",
m->mapx,m->mapy,
EXIT_X(m->map_object), EXIT_Y(m->map_object));
if(m->map_object->msg!=NULL)
LOG(llevError,"Message:\n%s",m->map_object->msg);
if(m->tmpname!=NULL)
LOG(llevError,"Tmpname: %s\n",m->tmpname);
LOG(llevError,"Difficulty: %d\n",m->difficulty);
#ifdef USE_LIGHTING
LOG(llevError,"Darkness: %d\n",m->darkness);
#endif
}
/*
* Prints out debug-information about all maps.
*/
void dump_all_maps() {
mapstruct *m;
for(m=first_map;m!=NULL;m=m->next) {
LOG(llevError,"Map %s status %d.\n",m->path,m->in_memory);
LOG(llevError,"Tmpname: %s\n",m->tmpname);
}
}
/*
* Returns true if a wall is present in a given location.
*/
int wall(mapstruct *m, int x,int y) {
if (out_of_map(m,x,y))
return 1;
return (get_map(m,x,y))->flags & P_NO_PASS;
}
/*
* Returns true if it's impossible to see through the given coordinate
* in the given map.
*/
int blocks_view(mapstruct *m, int x, int y) {
if(out_of_map(m,x,y))
return 1;
return (get_map(m,x,y))->flags & P_BLOCKSVIEW;
}
/*
* Returns true if the given coordinate in the given map blocks magic.
*/
int blocks_magic(mapstruct *m, int x, int y) {
if(out_of_map(m,x,y))
return 1;
return (get_map(m,x,y))->flags & P_NO_MAGIC;
}
/*
* Returns true if clerical spells cannot work here
*/
int blocks_cleric(mapstruct *m, int x, int y) {
if(out_of_map(m,x,y))
return 1;
return (get_map(m,x,y))->flags & P_NO_CLERIC;
}
/*
* Returns true if the given coordinate in the given map blocks passage.
*/
int blocked(mapstruct *m, int x, int y) {
int r;
MapLook *f;
if(out_of_map(m,x,y))
return 1;
f = get_map(m,x,y);
r = f->flags & (P_NO_PASS | P_IS_ALIVE);
return r;
}
/*
* Returns true if the given coordinate in the map where the given object
* is, blocks the given object (which may be multi-part)
*/
int blocked_link(object *ob, int x, int y) {
object *tmp;
if(out_of_map(ob->map,x,y))
return 1;
if (!blocked(ob->map,x,y)) /* no need to go further */
return 0;
if(ob->head != NULL)
ob=ob->head;
for(tmp = get_map_ob(ob->map,x,y); tmp!= NULL; tmp = tmp->above)
if (QUERY_FLAG(tmp,FLAG_NO_PASS) || (QUERY_FLAG(tmp,FLAG_ALIVE) &&
tmp->head != ob && tmp != ob))
return 1;
return 0;
}
/*
* Eneq(@csd.uu.se): This is a new version of blocked, this one handles objects
* that can be passed through by monsters with the CAN_PASS_THRU defined.
* Returns 1 if the object can not pass through that space.
*/
int blocked_two(object *op, int x,int y) {
object *tmp;
if(out_of_map(op->map,x,y))
return 1;
tmp=get_map_ob(op->map,x,y);
for(tmp=get_map_ob(op->map,x,y);tmp!=NULL;tmp=tmp->above)
if((QUERY_FLAG(tmp,FLAG_ALIVE)&&tmp->type!=DOOR)||
(QUERY_FLAG(tmp,FLAG_NO_PASS)&&!QUERY_FLAG(tmp,FLAG_PASS_THRU))||
(QUERY_FLAG(tmp,FLAG_NO_PASS)&&QUERY_FLAG(tmp,FLAG_PASS_THRU)&&
!QUERY_FLAG(op,FLAG_CAN_PASS_THRU)))
return 1;
return 0;
}
/*
* Returns true if the given archetype can't fit in the given spot.
*/
int arch_blocked(archetype *at,mapstruct *m,int x,int y) {
archetype *tmp;
if(at==NULL)
return blocked(m,x,y);
for(tmp=at;tmp!=NULL;tmp=tmp->more)
if(blocked(m,x+tmp->clone.x,y+tmp->clone.y))
return 1;
return 0;
}
/*
* Returns true if the given archetype can't fit into the map at the
* given spot (some part of it is outside the map-boundaries).
*/
int arch_out_of_map(archetype *at,mapstruct *m,int x,int y) {
archetype *tmp;
if(at==NULL)
return out_of_map(m,x,y);
for(tmp=at;tmp!=NULL;tmp=tmp->more)
if(out_of_map(m,x+tmp->clone.x,y+tmp->clone.y))
return 1;
return 0;
}
/*
* Goes through all objects in the given map, and does a sanity-check
* on all pointers.
*/
void refresh_map(mapstruct *m) {
int x,y;
object *tmp,*tmp2=NULL,*active=NULL;
if(m==NULL || m->in_memory != MAP_IN_MEMORY)
return;
for(x=0;x<m->mapx;x++)
for(y=0;y<m->mapy;y++) {
/* Eneq(@csd.uu.se): Hunting down inappropriate objects in the map. The game
sometime hangs and tries to remove_removed objects etc. */
for(active=tmp=get_map_ob(m,x,y);tmp!=NULL;tmp=tmp2)
{ /* how can we get objects with pointer<1024, mta */
if (tmp < (object *) 1024) /* map is uninitialized? */
break;
tmp2=tmp->above;
active=tmp;
if (QUERY_FLAG(tmp,FLAG_REMOVED)) {
LOG(llevError,"Crossfire: Found removed object in map.\n");
active=(tmp->above==NULL?tmp->below:tmp->above);
if (tmp->below==NULL&&tmp2==NULL)
m->map_ob[x+m->mapx*y]=NULL;
else if (tmp->below==NULL&&tmp2!=NULL) {
tmp2->below=NULL;
m->map_ob[x+m->mapx*y]=tmp2;
} else if (tmp->below!=NULL&&tmp2==NULL)
tmp->below->above=NULL;
else if (tmp->below!=NULL&&tmp2!=NULL) {
tmp->below->above=tmp2;
tmp2->below=tmp->below;
}
if (!QUERY_FLAG(tmp,FLAG_FREED))
free_object(tmp);
}
if (QUERY_FLAG(tmp,FLAG_FREED)&&!QUERY_FLAG(tmp,FLAG_REMOVED)) {
LOG(llevError, "Crossfire: Found freed object in map.\n");
remove_ob(tmp);
}
}
if(active != NULL)
update_object(active);
}
}
/*
* open_and_uncompress() first searches for the original filename.
* if it exist, then it opens it and returns the file-pointer.
* if not, it does two things depending on the flag. If the flag
* is set, it tries to create the original file by uncompressing a .Z file.
* If the flag is not set, it creates a pipe that is used for
* reading the file (NOTE - you can not use fseek on pipes)
*
* The compressed pointer is set to nonzero if the file is
* compressed (and thus, fp is actually a pipe.) It returns 0
* if it is a normal file
*
* (Note, the COMPRESS_SUFFIX is used instead of ".Z", thus it can easily
* be changed in the config file.)
*/
FILE *open_and_uncompress(char *name,int flag, int *compressed) {
FILE *fp;
char buf[MAX_BUF],buf2[MAX_BUF], *bufend;
int try_once = 0;
strcpy(buf, name);
bufend = buf + strlen(buf);
/* LOG(llevDebug, "open_and_uncompress(%s)\n", name);
*/
/* strip off any compression prefixes that may exist */
for (*compressed = 0; *compressed < NROF_COMPRESS_METHODS; (*compressed)++) {
if ((uncomp[*compressed][0]) &&
(!strcmp(uncomp[*compressed][0], bufend - strlen(uncomp[*compressed][0])))) {
buf[strlen(buf) - strlen(uncomp[*compressed][0])] = '\0';
bufend = buf + strlen(buf);
}
}
for (*compressed = 0; *compressed < NROF_COMPRESS_METHODS; (*compressed)++) {
struct stat statbuf;
if (uncomp[*compressed][0])
strcpy(bufend, uncomp[*compressed][0]);
if (stat(buf, &statbuf)) {
/* LOG(llevDebug, "Failed to stat %s\n", buf);
*/
continue;
}
/* LOG(llevDebug, "Found file %s\n", buf);
*/
if (uncomp[*compressed][0]) {
strcpy(buf2, uncomp[*compressed][1]);
strcat(buf2, " < ");
strcat(buf2, buf);
if (flag) {
int i;
if (try_once) {
LOG(llevError, "Failed to open %s after decompression.\n", name);
return NULL;
}
try_once = 1;
strcat(buf2, " > ");
strcat(buf2, name);
LOG(llevDebug, "system(%s)\n", buf2);
if ((i=system(buf2))) {
LOG(llevError, "system(%s) returned %d\n", buf2, i);
return NULL;
}
unlink(buf); /* Delete the original */
*compressed = '\0'; /* Restart the loop from the beginning */
chmod(name, statbuf.st_mode);
continue;
}
if ((fp = popen(buf2, "r")) != NULL)
return fp;
} else if((fp=fopen(name,"r"))!=NULL)
return fp;
}
LOG(llevDebug, "Can't open %s\n", name);
return NULL;
}
/*
* See open_and_uncompress().
*/
void close_and_delete(FILE *fp, int compressed) {
if (compressed)
pclose(fp);
else
fclose(fp);
}
/* When the map is loaded, load_object does not actually insert objects
* into inventory, but just links them. What this does is go through
* and insert them properly.
* The object 'container' is the object that contains the inventory.
* This is needed so that we can update the containers weight.
*/
void fix_container(object *container)
{
object *tmp=container->inv, *next;
container->inv=NULL;
while (tmp!=NULL) {
next = tmp->below;
if (tmp->inv)
fix_container(tmp);
(void) insert_ob_in_ob(tmp,container);
tmp = next;
}
/* sum_weight will go through and calculate what all the containers are
* carrying.
*/
sum_weight(container);
}
/*
* Loads (ands parses) the objects into a given map from the specified
* file pointer.
* If block is true, the game will be blocked until all objects have
* been loaded (this is needed to avoid certain critical regions).
*/
void load_objects (mapstruct *m, FILE *fp, int block) {
int i;
#if defined(PROCESS_WHILE_LOADING) && CHECK_ACTIVE_MAPS
int j = 0;
#endif
object *op, *prev=NULL,*last_more=NULL;
MapLook maplook;
op=get_object();
op->map = m; /* To handle buttons correctly */
while((i=load_object(fp,op))) {
/* if the archetype for the object is null, means that we
* got an invalid object. Don't do anythign with it - the game
* or editor will not be able to do anything with it either.
*/
if (op->arch==NULL) {
if (op->name!=NULL)
LOG(llevDebug,"Discarded object %s - invalid archetype.\n",op->name);
continue;
}
/* this hack allows for 'unused' altars (see below) to have a
* random god (see the 'god table' in god.c) associated w/ it -b.t.
*/
#ifdef MULTIPLE_GODS
if(op->type==ALTAR && !op->value && !editor && !op->stats.sp)
if(baptize_altar(op))
LOG(llevDebug,"Baptized altar to %s\n",op->title);
#endif
switch(i) {
case 1:
insert_ob_in_map(op,m);
if (op->inv) fix_container(op);
prev=op,last_more=op;
break;
case 2:
op = insert_ob_in_ob(op,prev);
break;
case 3:
insert_ob_in_map(op,m);
op->head=prev,last_more->more=op,last_more=op;
break;
}
/* Small hack to set the floor object for the maps. This
* increases the performance for color pixmaps, as not the floor
* and top object are easily accessible. Before, it had to
* search through the objects for the map space to find the
* floor.
*
* This should probably be someplace else, but I could not
* figure out the set_map_ob function, which then calls the
* set_map function, in where the floor is determined.
* I am also assuming that the floor can not change. As such,
* the floor only needs to be determined once.
* Mark Wedel (master@rahul.net)
*/
if (QUERY_FLAG(op,FLAG_IS_FLOOR)) {
maplook.face = op->face;
maplook.flags = 0;
m->floor[op->x+ m->mapx*op->y] = maplook;
}
op=get_object();
op->map = m;
#if defined(PROCESS_WHILE_LOADING) && CHECK_ACTIVE_MAPS
if(!block && j++>CHECK_ACTIVE_MAPS) {
j=0;
/* We don't want to freeze the whole game too long */
(*process_active_maps_func)();
}
#endif
}
free_object(op);
}
void save_objects (mapstruct *m, FILE *fp, FILE *fp2) {
int i, j = 0;
object *op, *tmp, *otmp;
#if 0 /*defined(PROCESS_WHILE_LOADING) && CHECK_ACTIVE_MAPS*/
int elapsed = 0;
#endif
/* first pass - save one-part objects */
for(i = 0; i < m->mapx; i++)
for (j = 0; j < m->mapy; j++)
for(op = get_map_ob (m, i, j); op; op = otmp) {
otmp = op->above;
if(op->type == PLAYER) {
LOG(llevDebug, "Player on map that is being saved\n");
continue;
}
if (op->head || op->more || op->owner)
continue;
save_object(QUERY_FLAG(op, FLAG_UNIQUE) ? fp2 : fp, op, 3);
/* don't check and don't remove */
/* Would be nice to let players still do stuff while a map is saving.
* The problem is, having a player enter a map that is in the process
* of being saved. The maps needs to finish saving, and then be
* re-loaded.
*/
#if 0 /*defined(PROCESS_WHILE_LOADING) && CHECK_ACTIVE_MAPS*/
if(elapsed++>CHECK_ACTIVE_MAPS) {
elapsed=0;
/* We don't want to freeze the whole game too long */
(*process_active_maps_func)();
}
#endif
}
/* second pass - save multi-part objects */
for(i = 0; i < m->mapx; i++)
for (j = 0; j < m->mapy; j++)
for(op = get_map_ob (m, i, j); op; op = otmp) {
otmp = op->above;
if(op->type == PLAYER) {
LOG(llevDebug, "Player on map that is being saved\n");
continue;
}
if (op->head || !op->more || op->owner)
continue;
save_object(QUERY_FLAG(op, FLAG_UNIQUE) ? fp2 : fp, op, 3);
/* don't check and don't remove */
for (tmp = op->more; tmp; tmp = tmp->more) {
fprintf (QUERY_FLAG(tmp, FLAG_UNIQUE) ? fp2:fp, "More\n");
save_object(QUERY_FLAG(tmp, FLAG_UNIQUE) ? fp2:fp, tmp, 3);
/* don't check and don't remove */
}
#if 0 /*defined(PROCESS_WHILE_LOADING) && CHECK_ACTIVE_MAPS*/
if(elapsed++>CHECK_ACTIVE_MAPS) {
elapsed=0;
/* We don't want to freeze the whole game too long */
(*process_active_maps_func)();
}
#endif
}
}
/*
* Opens the file "filename" and reads information about the map
* from the given file, and stores it in a newly allocated
* mapstruct. A pointer to this structure is returned, or NULL on failure.
* If block is true, it will block the game while reading the objects.
*/
mapstruct *load_original_map(char *filename, int block) {
FILE *fp;
mapstruct *m;
object *op;
int comp;
LOG(llevDebug, "load_original_map: %s\n", filename);
if((fp=open_and_uncompress(create_pathname(filename), 0, &comp))==NULL) {
LOG(llevError,"Can't open %s\n", create_pathname(filename));
perror("Can't read map file");
return (NULL);
}
op = get_object();
if (!load_object(fp, op) || op->type != MAP) {
LOG(llevError,"Error in map (%s) - map object not found\n", filename);
close_and_delete(fp, comp);
return (NULL);
}
if (op->arch == NULL) {
LOG(llevDebug, "Mapobject had no archetype\n");
op->arch = find_archetype("map");
if (op->arch == NULL)
LOG(llevError, "Archetype map lacks.");
if (op->arch->clone.type != MAP)
LOG(llevDebug, "Archetype map has wrong type.");
}
m = get_empty_map (op->x, op->y);
strcpy (m->path, filename);
m->compressed = comp;
if (m->map_object)
free_object (m->map_object);
m->map_object = op;
op->map = m;
m->in_memory=MAP_LOADING;
load_objects (m, fp, block);
close_and_delete(fp, comp);
m->in_memory=MAP_IN_MEMORY;
m->difficulty=calculate_difficulty(m);
m->darkness=m->map_object->invisible;
m->do_los=0;
set_map_reset_time(m);
return (m);
}
/*
* Loads a map, which has been loaded earlier, from file.
*/
void load_temporary_map(mapstruct *m) {
FILE *fp;
object *op;
int comp;
if (!m->tmpname) {
LOG(llevError, "No temporary filename for map %s\n", m->path);
m = load_original_map(m->path,0);
return;
}
if((fp=open_and_uncompress(m->tmpname,0, &comp))==NULL) {
LOG(llevError,"Can't open %s\n",m->tmpname);
perror("Can't read map file");
m = load_original_map(m->path,0);
return;
}
op = get_object();
load_object(fp,op);
if (op->arch == NULL || op->type != MAP) {
LOG(llevError,"Error in temporary map '%s'\n", m->path);
m = load_original_map(m->path,0);
return;
}
m->compressed = comp;
if (m->map_object)
free_object (m->map_object);
m->map_object = op;
allocate_map(m);
clear_map(m);
m->in_memory=MAP_LOADING;
load_objects (m, fp, 0);
close_and_delete(fp, comp);
m->in_memory=MAP_IN_MEMORY;
}
#ifdef UNIQUE_ITEMS
void merge_files (char *s1, char *s2)
{
char buf[MAX_BUF];
FILE *fp1, *fp2;
int i;
if ((fp1 = fopen (s1, "a+")) == NULL) {
LOG(llevError, "Can't open file for append '%s'\n", s1);
return;
}
if ((fp2 = fopen (s2, "r")) == NULL) {
LOG(llevError, "Can't open file for reading '%s'\n", s2);
fclose (fp1);
return;
}
while ((i = fread(buf, 1, MAX_BUF, fp2)) > 0)
fwrite (buf, 1, i, fp1);
fclose (fp1);
fclose (fp2);
}
/*
* returns version number if s1 is filename which is for map s2
*/
int match_filename(char *s1, char *s2)
{
int n;
if (*s2 == '/')
s2++;
while (*s2) {
if (*s1 != *s2 && *s2 != '/' && *s1 != '@')
return 0;
s1++, s2++;
}
return sscanf(s1, ".v%d", &n) == 1 ? n : 0;
}
void delete_unique_items(mapstruct *m)
{
int i,j;
object *op, *next;
for(i=0; i<m->map_object->x; i++)
for(j=0; j<m->map_object->y; j++)
for (op=get_map_ob(m, i, j); op; op=next) {
next = op->above;
if(op->head == NULL && QUERY_FLAG(op, FLAG_UNIQUE)) {
clean_object(op);
remove_ob(op);
free_object(op);
}
}
}
/*
* Loads unique objects from file(s) into the map which is in memory
*/
void load_unique_objects(mapstruct *m) {
FILE *fp;
int comp;
char firstname[MAX_BUF];
#ifdef LOCK_ITEMS
int v, firstlock = 0;
DIR *dirptr;
struct xdirect *dp;
if ((dirptr = opendir(create_items_path(""))) == NULL) {
LOG(llevDebug,"Can't open dir %s\n", create_items_path(""));
return;
}
while ((dp = xreaddir(dirptr,0)) != NULL ) {
if ((v = match_filename(dp->d_name, m->path))) {
if (lock_unique_items(m, v) != 1) {
LOG(llevDebug,"Unique items are already locked.\n");
continue;
}
if (firstlock == 0) {
strcpy (firstname, create_items_path(dp->d_name));
firstlock = v;
} else {
merge_files (firstname, create_items_path(dp->d_name));
unlink (create_items_path(dp->d_name));
unlock_unique_items(m); /* clears lock_items, so */
m->lock_items = firstlock; /* put original lock back */
}
}
}
closedir(dirptr);
if (firstlock == 0)
return;
#else
strcpy (firstname, create_items_path(m->path));
strcat (firstname, ".v00");
#endif /* LOCK_ITEMS */
if(! (fp=open_and_uncompress(firstname, 0, &comp))) {
LOG(llevDebug, "Can't open unique items file %s\n", firstname);
return;
}
m->in_memory=MAP_LOADING;
if (m->tmpname == NULL) /* if we have loaded unique items from */
delete_unique_items(m); /* original map before, don't duplicate them */
load_objects (m, fp, 0);
close_and_delete(fp, comp);
m->in_memory=MAP_IN_MEMORY;
}
#endif
#define MAXQUEUE 10000
static object* ob_queue[MAXQUEUE];
static int ob_queue_start=0;
static int ob_queue_end=0;
void queue_push(object* m_ob){
ob_queue[ob_queue_end++]=m_ob;
if(ob_queue_end==MAXQUEUE)
ob_queue_end=0;
}
object *queue_pop(void){
object* m_ob;
if(ob_queue_start==ob_queue_end)
return(NULL);
m_ob=ob_queue[ob_queue_start++];
if(ob_queue_start==MAXQUEUE)
ob_queue_start=0;
return(m_ob);
}
/*
* Saves a map to file. If flag is set, it is saved into the same
* file it was (originally) loaded from. Otherwise a temporary
* filename will be genarated, and the file will be stored there.
* The temporary filename will be stored in the mapstructure.
*/
int new_save_map(mapstruct *m, int flag) {
FILE *fp;
char filename[MAX_BUF];
#ifdef UNIQUE_ITEMS
FILE *fp2;
char buf[MAX_BUF];
#endif
if (flag && !*m->path) {
LOG(llevError,"Tried to save map without path.\n");
return -1;
}
if(flag) {
strcpy (filename, create_pathname (m->path));
/* If the compression suffix already exists on the filename, don't
* put it on again. This nasty looking strcmp checks to see if the
* compression suffix is at the end of the filename already.
*/
if (m->compressed &&
strcmp((filename + strlen(filename)-strlen(uncomp[m->compressed][0])),
uncomp[m->compressed][0]))
strcat(filename, uncomp[m->compressed][0]);
make_path_to_file (filename);
} else {
if (!m->tmpname)
m->tmpname = tempnam(TMPDIR,NULL);
strcpy(filename, m->tmpname);
}
LOG(llevDebug,"Saving map %s\n",m->path);
m->in_memory = MAP_SAVING;
if (m->compressed && flag) { /* Compress if it isn't a temporary save */
char buf[MAX_BUF];
strcpy(buf, uncomp[m->compressed][2]);
strcat(buf, " > ");
strcat(buf, filename);
fp = popen(buf, "w");
} else
fp = fopen(filename, "w");
if(fp == NULL) {
perror("Can't open file for saving");
return -1;
}
if (!m->map_object) {
LOG(llevError, "no map object for map %s!\n", m->path);
return -1;
}
save_object (fp, m->map_object, 3);
#ifdef UNIQUE_ITEMS
/* In the game save unique items in the different file, but
* in the editor save them to the normal map file.
*/
fp2 = fp; /* save unique items into fp2 */
if (flag == 0) {
int lock = 0;
#ifdef LOCK_ITEMS
lock = m->lock_items;
if (lock == 0) {
do {
lock++;
} while (lock < 10 && lock_unique_items(m, lock) != 1);
if (lock >= 10) {
LOG(llevError, "Over 10 item locks in map %s!\n", m->path);
}
}
#endif
strcpy (buf, create_items_path (m->path));
sprintf (buf + strlen(buf), ".v%02d", lock);
if ((fp2 = fopen (buf, "w")) == NULL) {
LOG(llevError, "Can't open items file %s\n", buf);
}
save_objects (m, fp, fp2);
if (fp2 != NULL) {
if (ftell (fp2) == 0) {
/* remove the file if it's empty */
fclose (fp2);
unlink (buf);
} else {
fclose (fp2);
chmod (buf, SAVE_MODE);
}
}
#ifdef LOCK_ITEMS
unlock_unique_items(m);
#endif
} else { /* save same file when not playing, like in editor */
save_objects (m, fp, fp);
}
#else
save_objects (m, fp, fp);
#endif /* UNIQUE_ITEMS */
if (m->compressed && !flag)
pclose(fp);
else
fclose(fp);
#if 0
if (flag && m->read_only) /* Huh ! */
chmod (filename, 0644);
else
#endif
chmod (filename, 0644);
return 0;
}
/*
* If any directories in the given path doesn't exist, they are created.
*/
void make_path_to_file (char *filename)
{
char buf[MAX_BUF], *cp = buf;
struct stat statbuf;
if (!filename || !*filename)
return;
strcpy (buf, filename);
LOG(llevDebug, "make_path_tofile %s...", filename);
while ((cp = strchr (cp + 1, (int) '/'))) {
*cp = '\0';
LOG(llevDebug, "Checking %s...", buf);
if (stat (buf, &statbuf) || !S_ISDIR (statbuf.st_mode)) {
LOG(llevDebug, "Was not dir...");
if (mkdir (buf, 0777)) {
perror ("Couldn't make path to file");
return;
}
LOG(llevDebug, "Made dir.\n");
} else
LOG(llevDebug, "Was dir\n");
*cp = '/';
}
}
/*
* Clears the arrays containing object-pointers and outlook of a map.
*/
void clear_map(mapstruct *m) {
MapLook *aptr, *endptr;
endptr=m->map+m->mapx*m->mapy;
for(aptr=m->map; aptr < endptr; ++aptr)
*aptr = blank_look;
endptr=m->floor+m->mapx*m->mapy;
for(aptr=m->floor; aptr < endptr; ++aptr)
*aptr = blank_look;
#ifdef DOUBLE_FLOOR
endptr=m->floor2+m->mapx*m->mapy;
for(aptr=m->floor2; aptr < endptr; ++aptr)
*aptr = blank_look;
#endif
memset(m->map_ob, 0,sizeof(object *)*m->mapx*m->mapy);
}
/*
* This function relinks all _pointers_ to the objects from
* one map to another.
* Note: You can _not_ free the objects in the original map
* after this function has been called.
* (What happened to this function? It no longer copies the pointers! -Frank)
*/
void copy_map(mapstruct *m1, mapstruct *m2) {
int x,y;
strncpy(m2->path,m1->path,BIG_NAME);
x = m2->mapx;
y = m2->mapy;
copy_object (m1->map_object, m2->map_object);
m2->mapx = x;
m2->mapy = y;
for(x=0;x<m1->mapx&&x<m2->mapx;x++)
for(y=0;y<m1->mapy&&y<m2->mapy;y++) {
set_map(m2,x,y,get_map(m1,x,y));
}
}
/*
* This function, like copy_map(), relinks all _pointers_ from
* one map to the other map.
*/
void relink_objs_offset(mapstruct *m1, mapstruct *m2, int dx, int dy) {
int x,y;
object *ob;
for (x = 0; x < m1->mapx && x < m2->mapx + dx; x++)
for (y = 0; y < m1->mapy && y < m2->mapy + dy; y++) {
set_map(m2, x + dx, y + dy, get_map(m1, x, y));
set_map_ob(m2, x + dx, y + dy, get_map_ob(m1, x, y));
set_map_ob(m1, x, y, (object *) NULL);
for (ob = get_map_ob(m2, x + dx, y + dy); ob; ob = ob->above)
{
ob->x = x + dx;
ob->y = y + dy;
ob->map = m2;
}
}
}
/*
* Remove and free all objects in the inventory of the given object.
* object.c ?
*/
void clean_object(object *op)
{
object *tmp, *next;
for(tmp = op->inv; tmp; tmp = next)
{
next = tmp->below;
clean_object(tmp);
remove_ob(tmp);
free_object(tmp);
}
}
#if 0 /* Why make it so difficult? I believe the below is buggy */
void clean_object(object *op) {
object *tmp;
while((tmp=op->inv)!=NULL) {
clean_object(tmp);
/* Eneq(@csd.uu.se): We'll have to move up the inventory or else op->inv
always will point to the same object. */
if (tmp->below!=NULL)
tmp->below->above=NULL;
op->inv=tmp->below;
tmp->below=NULL;
remove_ob(tmp);
free_object(tmp);
}
}
#endif
/*
* Remove and free all objects in the given map.
*/
void free_all_objects(mapstruct *m) {
int i,j;
object *op;
for(i=0;i<m->mapx;i++)
for(j=0;j<m->mapy;j++) {
object *previous_obj=NULL;
while((op=get_map_ob(m,i,j))!=NULL) {
if (op==previous_obj)
{
LOG(llevDebug, "free_all_objects: Link error, bailing out.\n");
break;
}
previous_obj=op;
if(op->head!=NULL)
op = op->head;
/* If the map isn't in memory, free_object will remove and
* free objects in op's inventory. So let it do the job.
*/
if (m->in_memory==MAP_IN_MEMORY)
clean_object(op);
remove_ob(op);
free_object(op);
}
}
}
/*
* This function moves all objects from one map to another.
*
* move_all_objects(): Only used from the editor(s?)
* Yes. -Frank
*/
void move_all_objects(mapstruct *m1, mapstruct *m2) {
int i,j;
object *op;
for(i=0;i<m1->mapx&&i<m2->mapx;i++)
for(j=0;j<m1->mapy&&j<m2->mapy;j++) {
while((op=get_map_ob(m1,i,j))!=NULL&&op->head==NULL) {
remove_ob(op);
op->x=i,op->y=j; /* Not really needed */
insert_ob_in_map(op,m2);
}
}
free_all_objects(m1);
}
/*
* function: vanish mapstruct
* m : pointer to mapstruct, if NULL no action
*/
void delete_map(mapstruct *m) {
mapstruct *tmp;
if (!m)
return;
if (m->in_memory == MAP_IN_MEMORY)
free_map (m, 1);
if (m == first_map)
first_map = m->next;
else {
for (tmp = first_map; tmp && tmp->next != m; tmp = tmp->next);
if(tmp)
tmp->next = m->next;
}
free (m);
}
/*
* Frees everything allocated by the given mapstructure.
*/
void free_map(mapstruct *m,int flag) {
if (!m->in_memory) {
LOG(llevError,"Trying to free freed map.\n");
return;
}
if(flag)
free_all_objects(m);
m->in_memory=MAP_SWAPPED;
if (m->map != NULL)
CFREE(m->map);
if (m->map_ob != NULL)
CFREE(m->map_ob);
if (m->floor !=NULL)
CFREE(m->floor);
#ifdef DOUBLE_FLOOR
if (m->floor2 !=NULL)
CFREE(m->floor2);
#endif
m->map = NULL;/* It's being accessed again, incorrectly. Maybe this will */
/* make it get loaded back in. */
m->map_ob = NULL;
m->floor = NULL;
#ifdef DOUBLE_FLOOR
m->floor2 = NULL;
#endif
if (m->map_object)
free_object (m->map_object);
m->map_object=NULL;
if (m->buttons)
free_objectlinkpt(m->buttons);
m->buttons = NULL;
}
/*
* Allocates, initialises, and returns a pointer to a mapstruct.
*/
mapstruct *get_linked_map(char *path) {
mapstruct *map=(mapstruct *) CALLOC(1,sizeof(mapstruct));
mapstruct *mp;
if(map==NULL)
fatal(OUT_OF_MEMORY);
for(mp=first_map;mp!=NULL&&mp->next!=NULL;mp=mp->next);
if(mp==NULL)
first_map=map;
else
mp->next=map;
map->next=NULL;
map->path[0]='\0';
map->tmpname=NULL;
map->encounter = 0;
map->players=0;
map->in_memory=MAP_SWAPPED;
/* map->read_only=0;*/
map->timeout=0;
map->light = (objectlink *) NULL;
map->darkness = 0;
map->do_los=0;
map->pending = (objectlink *) NULL;
map->map_object = NULL;
map->buttons = NULL;
#if defined(UNIQUE_ITEMS) && defined(LOCK_ITEMS)
map->lock_items = 0;
#endif
map->compressed = 0;
return map;
}
/*
* Allocates the arrays contained in a mapstruct.
*/
void allocate_map(mapstruct *m) {
if(m->in_memory != MAP_SWAPPED )
return;
m->in_memory = MAP_IN_MEMORY;
m->map=(MapLook *) CALLOC(m->mapx*m->mapy,sizeof(MapLook));
m->floor=(MapLook *) CALLOC(m->mapx*m->mapy,sizeof(MapLook));
m->map_ob=(object **) CALLOC(m->mapx*m->mapy,sizeof(object *));
#ifdef DOUBLE_FLOOR
m->floor2=(MapLook *) CALLOC(m->mapx*m->mapy,sizeof(MapLook));
if(m->map==NULL||m->map_ob==NULL || m->floor==NULL || m->floor2==NULL)
fatal(OUT_OF_MEMORY);
#else
if(m->map==NULL||m->map_ob==NULL || m->floor==NULL)
fatal(OUT_OF_MEMORY);
#endif
}
/*
* Creates an empty map of the given size, and returns a pointer to it.
*/
mapstruct *get_empty_map (int sizex, int sizey) {
mapstruct *m = get_linked_map (NULL);
archetype *tmp;
for (tmp = first_archetype; tmp; tmp = tmp->next)
if (tmp->clone.type == MAP)
break;
if (tmp) {
m->map_object = ObjectCreateArch (tmp);
m->mapx = sizex;
m->mapy = sizey;
allocate_map (m);
clear_map (m);
} else {
m->map_object = NULL;
LOG (llevError, "no map archetypes\n");
}
return m;
}
/*
* Makes sure the given map is loaded and swapped in.
* Returns a pointer to the given map.
*/
mapstruct *ready_map_name(char *name, int flush) {
mapstruct *m;
if (!name)
return (NULL);
/* Have we been at this level before? */
m = has_been_loaded (name);
if (m && (m->in_memory == MAP_LOADING || m->in_memory == MAP_IN_MEMORY))
return m;
if (flush || !m || (m->reset_time != -1 && seconds() > m->reset_time)) {
/* first visit or time to reset */
if (m) {
clean_tmp_map(m); /* Doesn't make much difference */
delete_map(m);
}
/* create and load a map */
LOG(llevDebug, "Trying to load map %s.\n", create_pathname(name));
if (!(m = load_original_map(name, 0)))
return (NULL);
(*fix_auto_apply_func)(m); /* Chests which open as default */
#ifdef UNIQUE_ITEMS
if (!flush) /* load these only in the game */
load_unique_objects(m);
#endif
} else {
load_temporary_map (m);
#ifdef UNIQUE_ITEMS
load_unique_objects(m);
#endif
clean_tmp_map(m);
m->in_memory = MAP_IN_MEMORY;
/* tempnam() on sun systems (probably others) uses malloc
* to allocated space for the string. Free it here.
*/
free(m->tmpname);
m->tmpname = NULL;
/* It's going to be saved anew anyway */
}
/* In case other objects press some buttons down */
(*update_buttons_func)(m);
return m;
}
void no_maps_file(char *filename) {
LOG(llevError,"Can't open the %s file.\n",filename);
LOG(llevError,"If you have no maps, you must either make them yourself\n");
LOG(llevError,"with the mapeditor, or fetch them from ftp.ifi.uio.no\n");
LOG(llevError,"in the /pub/crossfire directory. Be sure not to fetch\n");
LOG(llevError,"maps belonging to a later version than this version.\n");
exit(-1);
}
void set_map_reset_time(mapstruct *map) {
#ifdef MAP_RESET
#ifdef MAP_MAXRESET
if (MAP_RESETTIME(map)>MAP_MAXRESET)
map->reset_time = seconds() + MAP_MAXRESET;
else
#endif /* MAP_MAXRESET */
map->reset_time = seconds() + MAP_RESETTIME (map);
#else
map->reset_time = (-1); /* Will never be reset */
#endif
}
/*
* This routine is supposed to find out which level the players should have
* before visiting this map. It is used to calculate which bonuses to put
* on magic items.
*
* Difficulty can now be set by the map creature. If the value stored
* in the map is zero, then use this routine.
*/
int calculate_difficulty(mapstruct *m) {
object *op;
archetype *at;
int x,y;
int diff=0;
int total_exp=0,exp_pr_sq;
int i;
if (MAP_DIFFICULTY(m)) {
LOG(llevDebug, "Using stored map difficulty: %d\n", MAP_DIFFICULTY(m));
return MAP_DIFFICULTY(m);
}
for(x=0;x<m->mapx;x++)
for(y=0;y<m->mapy;y++)
for(op=get_map_ob(m,x,y);op!=NULL;op=op->above) {
if(QUERY_FLAG(op,FLAG_MONSTER))
total_exp+=op->stats.exp;
if(QUERY_FLAG(op,FLAG_GENERATOR)) {
total_exp+=op->stats.exp;
at=type_to_archetype(GENERATE_TYPE(op));
if(at!=NULL)
total_exp+=at->clone.stats.exp*8;
}
}
#ifdef NEWCALC
(int)exp_pr_sq=((double)1000*total_exp)/(m->mapx*m->mapy+1);
for(i=20;i>0;i--)
if(exp_pr_sq>level_exp(i,1.0)) {
diff=i;
break;
}
#else
exp_pr_sq=((double)1000*total_exp)/(m->mapx*m->mapy+1);
diff=20;
for(i=1;i<20;i++)
if(exp_pr_sq<=level_exp(i,1.0)) {
diff=i;
break;
}
#endif
return diff;
}
void clean_tmp_map(mapstruct *m) {
if(m->tmpname == NULL)
return;
(void) unlink(m->tmpname);
}
/*
* member: copy by translate objects from source to a new map
* source: -map
* width : width of target map
* height: height of target map
* dx : positive translate to right
* dy : positive translate to down
*/
mapstruct *MapMoveScrollResize(mapstruct *source,
int width, int height, int dx, int dy)
{
mapstruct *target;
object *obj,*prt; /* PaRT of obj */
int x,y,sx = source->mapx, sy = source->mapy;
int linked = 0, link=0;
if (!width) width = sx;
if (!height) height = sy;
target = get_empty_map (width, height);
strncpy (target->path, source->path, BIG_NAME);
copy_object (source->map_object, target->map_object);
target->mapx = width;
target->mapy = height;
if(dx < 0) dx += target->mapx;
if(dy < 0) dy += target->mapy;
for(y=0; y < sy && y < target->mapy; y++)
for(x=0; x < sx && x < target->mapx; x++)
while((obj = get_map_ob(source,x,y)) && !obj->head) {
if ((linked = QUERY_FLAG (obj,FLAG_IS_LINKED))) {
link = get_button_value (obj);
remove_button_link (obj);
}
remove_ob(obj);
for(prt = obj; prt; prt = prt->more) {
prt->x += dx;
prt->x %= target->mapx; /* it can be split by edge */
prt->y += dy; /* designers problem to fix */
prt->y %= target->mapy;
}
insert_ob_in_map(obj,target);
if (linked)
add_button_link(obj, target, link);
}
/*free_all_objects(source);*/
free_map (source, 1);
delete_map (source);
return target;
}
/*
* member: copy by translate objects from source to target
* target: -map
* source: -map
* dx : positive translate to right
* dy : positive translate to down
*/
void MapMoveScroll(mapstruct *target, mapstruct *source, int dx, int dy)
{
object *obj,*prt; /* PaRT of obj */
int x,y;
if(dx < 0) dx += target->mapx;
if(dy < 0) dy += target->mapy;
for(y=0; y < source->mapy && y < target->mapy; y++)
for(x=0; x < source->mapx && x < target->mapx; x++)
while((obj = get_map_ob(source,x,y)) && !obj->head) {
remove_ob(obj);
for(prt = obj; prt; prt = prt->more) {
prt->x += dx;
prt->x %= target->mapx; /* it can be split by edge */
prt->y += dy; /* designers problem to fix */
prt->y %= target->mapy;
}
insert_ob_in_map(obj,target);
}
free_all_objects(source);
}
object * MapGetRealObject (mapstruct * emap, int x, int y, int z)
{
object *tmp = MapGetObjectZ (emap, x, y, z);
return tmp ? (tmp->head ? tmp->head : tmp) : tmp;
}
int MapInsertObjectZ(mapstruct *emap,object *o,int x, int y, int z)
{
object *op, *above, *below;
if (o->more)
MapInsertObjectZ (emap,o->more, x, y, z);
o->x += x;
o->y += y;
o->map = emap;
CLEAR_FLAG(o,FLAG_REMOVED);
op = get_map_ob (emap, o->x, o->y);
if (z < 0) {
above = op;
below = NULL;
} else {
while (op && op->above)
op = op->above;
above = NULL;
below = op;
while (op && z-- > 0) {
above = op;
below = op = op->below;
}
}
o->below = below;
o->above = above;
if (above)
above->below = o;
else {
MapLook f;
f.flags = 0;
f.face = o->face;
set_map (emap, o->x, o->y, &f);
}
if (below)
below->above = o;
else
set_map_ob (emap, o->x, o->y, o);
return (0);
}
int MapObjectOut (mapstruct *target, object *obj, int x, int y) {
object *tmp;
for(tmp = obj; tmp; tmp = tmp->more)
if(out_of_map(target,x + tmp->x,y + tmp->y)) return 1;
return 0;
}
object * MapGetObjectZ (mapstruct * emap, int x, int y, int z)
{
object *op;
if (!emap || out_of_map (emap, x, y))
return (NULL);
op = get_map_ob (emap, x, y);
while (op && op->above)
op = op->above;
while (op && z-- > 0)
op = op->below;
return (op);
}
/* baptize_altar() - (cosmetically) change the name to that of the
* god in question, then set the title for later use. -b.t.
*/
int baptize_altar(object *op) {
char buf[MAX_BUF];
int i = RANDOM()%NROFGODS;
/* if the object name hasnt' been changed, we give it a random god */
if(!strcmp(op->name,op->arch->clone.name)) {
if(op->name!=NULL) free_string(op->name);
sprintf(buf,"%s of %s",op->name,Gods[i].name);
op->name = add_string(buf);
}
if(!op->title) {
op->title=add_string(Gods[i].name);
return 1;
}
return 0;
}